// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/midi/midi_manager_win.h"

#include <windows.h>
#include <ks.h>
#include <ksmedia.h>
#include <mmreg.h>
// Prevent unnecessary functions from being included from <mmsystem.h>
#define MMNODRV
#define MMNOSOUND
#define MMNOWAVE
#define MMNOAUX
#define MMNOMIXER
#define MMNOTIMER
#define MMNOJOY
#define MMNOMCI
#define MMNOMMIO
#include <mmsystem.h>
#include <stddef.h>

#include <algorithm>
#include <functional>
#include <queue>
#include <string>

#include "base/bind.h"
#include "base/containers/hash_tables.h"
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system_monitor/system_monitor.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "base/win/message_window.h"
#include "base/win/windows_version.h"
#include "device/usb/usb_ids.h"
#include "media/midi/message_util.h"
#include "media/midi/midi_manager_winrt.h"
#include "media/midi/midi_message_queue.h"
#include "media/midi/midi_port_info.h"
#include "media/midi/midi_switches.h"

namespace midi {
namespace {

    using mojom::PortState;
    using mojom::Result;

    static const size_t kBufferLength = 32 * 1024;

    // We assume that nullpter represents an invalid MIDI handle.
    const HMIDIIN kInvalidMidiInHandle = nullptr;
    const HMIDIOUT kInvalidMidiOutHandle = nullptr;

    std::string GetInErrorMessage(MMRESULT result)
    {
        wchar_t text[MAXERRORLENGTH];
        MMRESULT get_result = midiInGetErrorText(result, text, arraysize(text));
        if (get_result != MMSYSERR_NOERROR) {
            DLOG(ERROR) << "Failed to get error message."
                        << " original error: " << result
                        << " midiInGetErrorText error: " << get_result;
            return std::string();
        }
        return base::WideToUTF8(text);
    }

    std::string GetOutErrorMessage(MMRESULT result)
    {
        wchar_t text[MAXERRORLENGTH];
        MMRESULT get_result = midiOutGetErrorText(result, text, arraysize(text));
        if (get_result != MMSYSERR_NOERROR) {
            DLOG(ERROR) << "Failed to get error message."
                        << " original error: " << result
                        << " midiOutGetErrorText error: " << get_result;
            return std::string();
        }
        return base::WideToUTF8(text);
    }

    std::string MmversionToString(MMVERSION version)
    {
        return base::StringPrintf("%d.%d", HIBYTE(version), LOBYTE(version));
    }

    void CloseOutputPortOnTaskThread(HMIDIOUT midi_out_handle)
    {
        midiOutClose(midi_out_handle);
    }

    class MIDIHDRDeleter {
    public:
        void operator()(MIDIHDR* header)
        {
            if (!header)
                return;
            delete[] static_cast<char*>(header->lpData);
            header->lpData = NULL;
            header->dwBufferLength = 0;
            delete header;
        }
    };

    using ScopedMIDIHDR = std::unique_ptr<MIDIHDR, MIDIHDRDeleter>;

    ScopedMIDIHDR CreateMIDIHDR(size_t size)
    {
        ScopedMIDIHDR header(new MIDIHDR);
        ZeroMemory(header.get(), sizeof(*header));
        header->lpData = new char[size];
        header->dwBufferLength = static_cast<DWORD>(size);
        return header;
    }

    void SendShortMidiMessageInternal(HMIDIOUT midi_out_handle,
        const std::vector<uint8_t>& message)
    {
        DCHECK_LE(message.size(), static_cast<size_t>(3))
            << "A short MIDI message should be up to 3 bytes.";

        DWORD packed_message = 0;
        for (size_t i = 0; i < message.size(); ++i)
            packed_message |= (static_cast<uint32_t>(message[i]) << (i * 8));
        MMRESULT result = midiOutShortMsg(midi_out_handle, packed_message);
        DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
            << "Failed to output short message: " << GetOutErrorMessage(result);
    }

    void SendLongMidiMessageInternal(HMIDIOUT midi_out_handle,
        const std::vector<uint8_t>& message)
    {
        // Implementation note:
        // Sending a long MIDI message can be performed synchronously or
        // asynchronously depending on the driver. There are 2 options to support both
        // cases:
        // 1) Call midiOutLongMsg() API and wait for its completion within this
        //   function. In this approach, we can avoid memory copy by directly pointing
        //   |message| as the data buffer to be sent.
        // 2) Allocate a buffer and copy |message| to it, then call midiOutLongMsg()
        //   API. The buffer will be freed in the MOM_DONE event hander, which tells
        //   us that the task of midiOutLongMsg() API is completed.
        // Here we choose option 2) in favor of asynchronous design.

        // Note for built-in USB-MIDI driver:
        // From an observation on Windows 7/8.1 with a USB-MIDI keyboard,
        // midiOutLongMsg() will be always blocked. Sending 64 bytes or less data
        // takes roughly 300 usecs. Sending 2048 bytes or more data takes roughly
        // |message.size() / (75 * 1024)| secs in practice. Here we put 256 KB size
        // limit on SysEx message, with hoping that midiOutLongMsg will be blocked at
        // most 4 sec or so with a typical USB-MIDI device.
        // TODO(crbug.com/383578): This restriction should be removed once Web MIDI
        // defines a standardized way to handle large sysex messages.
        const size_t kSysExSizeLimit = 256 * 1024;
        if (message.size() >= kSysExSizeLimit) {
            DVLOG(1) << "Ingnoreing SysEx message due to the size limit"
                     << ", size = " << message.size();
            return;
        }

        ScopedMIDIHDR midi_header(CreateMIDIHDR(message.size()));
        std::copy(message.begin(), message.end(), midi_header->lpData);

        MMRESULT result = midiOutPrepareHeader(midi_out_handle, midi_header.get(),
            sizeof(*midi_header));
        if (result != MMSYSERR_NOERROR) {
            DLOG(ERROR) << "Failed to prepare output buffer: "
                        << GetOutErrorMessage(result);
            return;
        }

        result = midiOutLongMsg(midi_out_handle, midi_header.get(), sizeof(*midi_header));
        if (result != MMSYSERR_NOERROR) {
            DLOG(ERROR) << "Failed to output long message: "
                        << GetOutErrorMessage(result);
            result = midiOutUnprepareHeader(midi_out_handle, midi_header.get(),
                sizeof(*midi_header));
            DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                << "Failed to uninitialize output buffer: "
                << GetOutErrorMessage(result);
            return;
        }

        // The ownership of |midi_header| is moved to MOM_DONE event handler.
        ignore_result(midi_header.release());
    }

    template <size_t array_size>
    base::string16 AsString16(const wchar_t (&buffer)[array_size])
    {
        size_t len = 0;
        for (len = 0; len < array_size; ++len) {
            if (buffer[len] == L'\0')
                break;
        }
        return base::string16(buffer, len);
    }

    struct MidiDeviceInfo final {
        explicit MidiDeviceInfo(const MIDIINCAPS2W& caps)
            : manufacturer_id(caps.wMid)
            , product_id(caps.wPid)
            , driver_version(caps.vDriverVersion)
            , product_name(AsString16(caps.szPname))
            , usb_vendor_id(ExtractUsbVendorIdIfExists(caps))
            , usb_product_id(ExtractUsbProductIdIfExists(caps))
            , is_usb_device(IsUsbDevice(caps))
            , is_software_synth(false)
        {
        }
        explicit MidiDeviceInfo(const MIDIOUTCAPS2W& caps)
            : manufacturer_id(caps.wMid)
            , product_id(caps.wPid)
            , driver_version(caps.vDriverVersion)
            , product_name(AsString16(caps.szPname))
            , usb_vendor_id(ExtractUsbVendorIdIfExists(caps))
            , usb_product_id(ExtractUsbProductIdIfExists(caps))
            , is_usb_device(IsUsbDevice(caps))
            , is_software_synth(IsSoftwareSynth(caps))
        {
        }
        explicit MidiDeviceInfo(const MidiDeviceInfo& info)
            : manufacturer_id(info.manufacturer_id)
            , product_id(info.product_id)
            , driver_version(info.driver_version)
            , product_name(info.product_name)
            , usb_vendor_id(info.usb_vendor_id)
            , usb_product_id(info.usb_product_id)
            , is_usb_device(info.is_usb_device)
            , is_software_synth(info.is_software_synth)
        {
        }
        // Currently only following entities are considered when testing the equality
        // of two MIDI devices.
        // TODO(toyoshim): Consider to calculate MIDIPort.id here and use it as the
        // key. See crbug.com/467448.  Then optimize the data for |MidiPortInfo|.
        const uint16_t manufacturer_id;
        const uint16_t product_id;
        const uint32_t driver_version;
        const base::string16 product_name;
        const uint16_t usb_vendor_id;
        const uint16_t usb_product_id;
        const bool is_usb_device;
        const bool is_software_synth;

        // Required to be used as the key of base::hash_map.
        bool operator==(const MidiDeviceInfo& that) const
        {
            return manufacturer_id == that.manufacturer_id && product_id == that.product_id && driver_version == that.driver_version && product_name == that.product_name && is_usb_device == that.is_usb_device && (is_usb_device && usb_vendor_id == that.usb_vendor_id && usb_product_id == that.usb_product_id);
        }

        // Hash function to be used in base::hash_map.
        struct Hasher {
            size_t operator()(const MidiDeviceInfo& info) const
            {
                size_t hash = info.manufacturer_id;
                hash *= 131;
                hash += info.product_id;
                hash *= 131;
                hash += info.driver_version;
                hash *= 131;
                hash += info.product_name.size();
                hash *= 131;
                if (!info.product_name.empty()) {
                    hash += info.product_name[0];
                }
                hash *= 131;
                hash += info.usb_vendor_id;
                hash *= 131;
                hash += info.usb_product_id;
                return hash;
            }
        };

    private:
        static bool IsUsbDevice(const MIDIINCAPS2W& caps)
        {
            return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) && IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
        }
        static bool IsUsbDevice(const MIDIOUTCAPS2W& caps)
        {
            return IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid) && IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid);
        }
        static bool IsSoftwareSynth(const MIDIOUTCAPS2W& caps)
        {
            return caps.wTechnology == MOD_SWSYNTH;
        }
        static uint16_t ExtractUsbVendorIdIfExists(const MIDIINCAPS2W& caps)
        {
            if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
                return 0;
            return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
        }
        static uint16_t ExtractUsbVendorIdIfExists(const MIDIOUTCAPS2W& caps)
        {
            if (!IS_COMPATIBLE_USBAUDIO_MID(&caps.ManufacturerGuid))
                return 0;
            return EXTRACT_USBAUDIO_MID(&caps.ManufacturerGuid);
        }
        static uint16_t ExtractUsbProductIdIfExists(const MIDIINCAPS2W& caps)
        {
            if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
                return 0;
            return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
        }
        static uint16_t ExtractUsbProductIdIfExists(const MIDIOUTCAPS2W& caps)
        {
            if (!IS_COMPATIBLE_USBAUDIO_PID(&caps.ProductGuid))
                return 0;
            return EXTRACT_USBAUDIO_PID(&caps.ProductGuid);
        }
    };

    std::string GetManufacturerName(const MidiDeviceInfo& info)
    {
        if (info.is_usb_device) {
            const char* name = device::UsbIds::GetVendorName(info.usb_vendor_id);
            return std::string(name ? name : "");
        }

        switch (info.manufacturer_id) {
        case MM_MICROSOFT:
            return "Microsoft Corporation";
        default:
            // TODO(toyoshim): Support other manufacture IDs.  crbug.com/472341.
            return "";
        }
    }

    bool IsUnsupportedDevice(const MidiDeviceInfo& info)
    {
        return info.is_software_synth && info.manufacturer_id == MM_MICROSOFT && (info.product_id == MM_MSFT_WDMAUDIO_MIDIOUT || info.product_id == MM_MSFT_GENERIC_MIDISYNTH);
    }

    using PortNumberCache = base::hash_map<MidiDeviceInfo,
        std::priority_queue<uint32_t,
            std::vector<uint32_t>,
            std::greater<uint32_t>>,
        MidiDeviceInfo::Hasher>;

    struct MidiInputDeviceState final
        : base::RefCountedThreadSafe<MidiInputDeviceState> {
        explicit MidiInputDeviceState(const MidiDeviceInfo& device_info)
            : device_info(device_info)
            , midi_handle(kInvalidMidiInHandle)
            , port_index(0)
            , port_age(0)
            , start_time_initialized(false)
        {
        }

        const MidiDeviceInfo device_info;
        HMIDIIN midi_handle;
        ScopedMIDIHDR midi_header;
        // Since Win32 multimedia system uses a relative time offset from when
        // |midiInStart| API is called, we need to record when it is called.
        base::TimeTicks start_time;
        // 0-based port index.  We will try to reuse the previous port index when the
        // MIDI device is closed then reopened.
        uint32_t port_index;
        // A sequence number which represents how many times |port_index| is reused.
        // We can remove this field if we decide not to clear unsent events
        // when the device is disconnected.
        // See https://github.com/WebAudio/web-midi-api/issues/133
        uint64_t port_age;
        // True if |start_time| is initialized. This field is not used so far, but
        // kept for the debugging purpose.
        bool start_time_initialized;

    private:
        friend class base::RefCountedThreadSafe<MidiInputDeviceState>;
        ~MidiInputDeviceState() { }
    };

    struct MidiOutputDeviceState final
        : base::RefCountedThreadSafe<MidiOutputDeviceState> {
        explicit MidiOutputDeviceState(const MidiDeviceInfo& device_info)
            : device_info(device_info)
            , midi_handle(kInvalidMidiOutHandle)
            , port_index(0)
            , port_age(0)
            , closed(false)
        {
        }

        const MidiDeviceInfo device_info;
        HMIDIOUT midi_handle;
        // 0-based port index.  We will try to reuse the previous port index when the
        // MIDI device is closed then reopened.
        uint32_t port_index;
        // A sequence number which represents how many times |port_index| is reused.
        // We can remove this field if we decide not to clear unsent events
        // when the device is disconnected.
        // See https://github.com/WebAudio/web-midi-api/issues/133
        uint64_t port_age;
        // True if the device is already closed and |midi_handle| is considered to be
        // invalid.
        // TODO(toyoshim): Use std::atomic<bool> when it is allowed in Chromium
        // project.
        volatile bool closed;

    private:
        friend class base::RefCountedThreadSafe<MidiOutputDeviceState>;
        ~MidiOutputDeviceState() { }
    };

    // The core logic of MIDI device handling for Windows. Basically this class is
    // shared among following 4 threads:
    //  1. Chrome IO Thread
    //  2. OS Multimedia Thread
    //  3. Task Thread
    //  4. Sender Thread
    //
    // Chrome IO Thread:
    //  MidiManager runs on Chrome IO thread. Device change notification is
    //  delivered to the thread through the SystemMonitor service.
    //  OnDevicesChanged() callback is invoked to update the MIDI device list.
    //  Note that in the current implementation we will try to open all the
    //  existing devices in practice. This is OK because trying to reopen a MIDI
    //  device that is already opened would simply fail, and there is no unwilling
    //  side effect.
    //
    // OS Multimedia Thread:
    //  This thread is maintained by the OS as a part of MIDI runtime, and
    //  responsible for receiving all the system initiated events such as device
    //  close, and receiving data. For performance reasons, most of potentially
    //  blocking operations will be dispatched into Task Thread.
    //
    // Task Thread:
    //  This thread will be used to call back following methods of MidiManager.
    //  - MidiManager::CompleteInitialization
    //  - MidiManager::AddInputPort
    //  - MidiManager::AddOutputPort
    //  - MidiManager::SetInputPortState
    //  - MidiManager::SetOutputPortState
    //  - MidiManager::ReceiveMidiData
    //
    // Sender Thread:
    //  This thread will be used to call Win32 APIs to send MIDI message at the
    //  specified time. We don't want to call MIDI send APIs on Task Thread
    //  because those APIs could be performed synchronously, hence they could block
    //  the caller thread for a while. See the comment in
    //  SendLongMidiMessageInternal for details. Currently we expect that the
    //  blocking time would be less than 1 second.
    class MidiServiceWinImpl : public MidiServiceWin,
                               public base::SystemMonitor::DevicesChangedObserver {
    public:
        MidiServiceWinImpl()
            : delegate_(nullptr)
            , sender_thread_("Windows MIDI sender thread")
            , task_thread_("Windows MIDI task thread")
            , destructor_started(false)
        {
        }

        ~MidiServiceWinImpl() final
        {
            // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
            // RemoveDevicesChangeObserver() should be called on the same thread.
            DCHECK(thread_checker_.CalledOnValidThread());

            destructor_started = true;
            base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this);
            {
                std::vector<HMIDIIN> input_devices;
                {
                    base::AutoLock auto_lock(input_ports_lock_);
                    for (auto it : input_device_map_)
                        input_devices.push_back(it.first);
                }
                {
                    for (auto* handle : input_devices) {
                        MMRESULT result = midiInClose(handle);
                        if (result == MIDIERR_STILLPLAYING) {
                            result = midiInReset(handle);
                            DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                                << "midiInReset failed: " << GetInErrorMessage(result);
                            result = midiInClose(handle);
                        }
                        DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                            << "midiInClose failed: " << GetInErrorMessage(result);
                    }
                }
            }
            {
                std::vector<HMIDIOUT> output_devices;
                {
                    base::AutoLock auto_lock(output_ports_lock_);
                    for (auto it : output_device_map_)
                        output_devices.push_back(it.first);
                }
                {
                    for (auto* handle : output_devices) {
                        MMRESULT result = midiOutClose(handle);
                        if (result == MIDIERR_STILLPLAYING) {
                            result = midiOutReset(handle);
                            DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                                << "midiOutReset failed: " << GetOutErrorMessage(result);
                            result = midiOutClose(handle);
                        }
                        DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                            << "midiOutClose failed: " << GetOutErrorMessage(result);
                    }
                }
            }
            sender_thread_.Stop();
            task_thread_.Stop();
        }

        // MidiServiceWin overrides:
        void InitializeAsync(MidiServiceWinDelegate* delegate) final
        {
            // Start() and Stop() of the threads, and AddDevicesChangeObserver() and
            // RemoveDevicesChangeObserver() should be called on the same thread.
            DCHECK(thread_checker_.CalledOnValidThread());

            delegate_ = delegate;

            sender_thread_.Start();
            task_thread_.Start();

            // Start monitoring device changes. This should start before the
            // following UpdateDeviceList() call not to miss the event happening
            // between the call and the observer registration.
            base::SystemMonitor::Get()->AddDevicesChangedObserver(this);

            UpdateDeviceList();

            task_thread_.task_runner()->PostTask(
                FROM_HERE,
                base::Bind(&MidiServiceWinImpl::CompleteInitializationOnTaskThread,
                    base::Unretained(this), Result::OK));
        }

        void SendMidiDataAsync(uint32_t port_number,
            const std::vector<uint8_t>& data,
            base::TimeTicks time) final
        {
            if (destructor_started) {
                LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
                              "being destructed.  port: "
                           << port_number;
                return;
            }
            auto state = GetOutputDeviceFromPort(port_number);
            if (!state) {
                LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
                           << "port: " << port_number;
                return;
            }
            if (state->closed) {
                LOG(ERROR)
                    << "ThreadSafeSendData failed because target port is already closed."
                    << "port: " << port_number;
                return;
            }
            const auto now = base::TimeTicks::Now();
            if (now < time) {
                sender_thread_.task_runner()->PostDelayedTask(
                    FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread, base::Unretained(this), port_number, state->port_age, data, time),
                    time - now);
            } else {
                sender_thread_.task_runner()->PostTask(
                    FROM_HERE, base::Bind(&MidiServiceWinImpl::SendOnSenderThread, base::Unretained(this), port_number, state->port_age, data, time));
            }
        }

        // base::SystemMonitor::DevicesChangedObserver overrides:
        void OnDevicesChanged(base::SystemMonitor::DeviceType device_type) final
        {
            DCHECK(thread_checker_.CalledOnValidThread());
            if (destructor_started)
                return;

            switch (device_type) {
            case base::SystemMonitor::DEVTYPE_AUDIO:
            case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE:
                // Add case of other unrelated device types here.
                return;
            case base::SystemMonitor::DEVTYPE_UNKNOWN:
                // Interested in MIDI devices. Try updating the device list.
                UpdateDeviceList();
                break;
                // No default here to capture new DeviceType by compile time.
            }
        }

    private:
        scoped_refptr<MidiInputDeviceState> GetInputDeviceFromHandle(
            HMIDIIN midi_handle)
        {
            base::AutoLock auto_lock(input_ports_lock_);
            const auto it = input_device_map_.find(midi_handle);
            return (it != input_device_map_.end() ? it->second : nullptr);
        }

        scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromHandle(
            HMIDIOUT midi_handle)
        {
            base::AutoLock auto_lock(output_ports_lock_);
            const auto it = output_device_map_.find(midi_handle);
            return (it != output_device_map_.end() ? it->second : nullptr);
        }

        scoped_refptr<MidiOutputDeviceState> GetOutputDeviceFromPort(
            uint32_t port_number)
        {
            base::AutoLock auto_lock(output_ports_lock_);
            if (output_ports_.size() <= port_number)
                return nullptr;
            return output_ports_[port_number];
        }

        void UpdateDeviceList()
        {
            task_thread_.task_runner()->PostTask(
                FROM_HERE, base::Bind(&MidiServiceWinImpl::UpdateDeviceListOnTaskThread, base::Unretained(this)));
        }

        /////////////////////////////////////////////////////////////////////////////
        // Callbacks on the OS multimedia thread.
        /////////////////////////////////////////////////////////////////////////////

        static void CALLBACK
        OnMidiInEventOnMainlyMultimediaThread(HMIDIIN midi_in_handle,
            UINT message,
            DWORD_PTR instance,
            DWORD_PTR param1,
            DWORD_PTR param2)
        {
            MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
            if (!self)
                return;
            switch (message) {
            case MIM_OPEN:
                self->OnMidiInOpen(midi_in_handle);
                break;
            case MIM_DATA:
                self->OnMidiInDataOnMultimediaThread(midi_in_handle, param1, param2);
                break;
            case MIM_LONGDATA:
                self->OnMidiInLongDataOnMultimediaThread(midi_in_handle, param1,
                    param2);
                break;
            case MIM_CLOSE:
                self->OnMidiInCloseOnMultimediaThread(midi_in_handle);
                break;
            }
        }

        void OnMidiInOpen(HMIDIIN midi_in_handle)
        {
            UINT device_id = 0;
            MMRESULT result = midiInGetID(midi_in_handle, &device_id);
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "midiInGetID failed: " << GetInErrorMessage(result);
                return;
            }
            MIDIINCAPS2W caps = {};
            result = midiInGetDevCaps(device_id, reinterpret_cast<LPMIDIINCAPSW>(&caps),
                sizeof(caps));
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "midiInGetDevCaps failed: " << GetInErrorMessage(result);
                return;
            }
            auto state = make_scoped_refptr(new MidiInputDeviceState(MidiDeviceInfo(caps)));
            state->midi_handle = midi_in_handle;
            state->midi_header = CreateMIDIHDR(kBufferLength);
            const auto& state_device_info = state->device_info;
            bool add_new_port = false;
            uint32_t port_number = 0;
            {
                base::AutoLock auto_lock(input_ports_lock_);
                const auto it = unused_input_ports_.find(state_device_info);
                if (it == unused_input_ports_.end()) {
                    port_number = static_cast<uint32_t>(input_ports_.size());
                    add_new_port = true;
                    input_ports_.push_back(nullptr);
                    input_ports_ages_.push_back(0);
                } else {
                    port_number = it->second.top();
                    it->second.pop();
                    if (it->second.empty()) {
                        unused_input_ports_.erase(it);
                    }
                }
                input_ports_[port_number] = state;

                input_ports_ages_[port_number] += 1;
                input_device_map_[input_ports_[port_number]->midi_handle] = input_ports_[port_number];
                input_ports_[port_number]->port_index = port_number;
                input_ports_[port_number]->port_age = input_ports_ages_[port_number];
            }
            // Several initial startup tasks cannot be done in MIM_OPEN handler.
            task_thread_.task_runner()->PostTask(
                FROM_HERE, base::Bind(&MidiServiceWinImpl::StartInputDeviceOnTaskThread, base::Unretained(this), midi_in_handle));
            if (add_new_port) {
                const MidiPortInfo port_info(
                    // TODO(toyoshim): Use a hash ID insted crbug.com/467448
                    base::IntToString(static_cast<int>(port_number)),
                    GetManufacturerName(state_device_info),
                    base::WideToUTF8(state_device_info.product_name),
                    MmversionToString(state_device_info.driver_version),
                    PortState::OPENED);
                task_thread_.task_runner()->PostTask(
                    FROM_HERE, base::Bind(&MidiServiceWinImpl::AddInputPortOnTaskThread, base::Unretained(this), port_info));
            } else {
                task_thread_.task_runner()->PostTask(
                    FROM_HERE,
                    base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
                        base::Unretained(this), port_number,
                        PortState::CONNECTED));
            }
        }

        void OnMidiInDataOnMultimediaThread(HMIDIIN midi_in_handle,
            DWORD_PTR param1,
            DWORD_PTR param2)
        {
            auto state = GetInputDeviceFromHandle(midi_in_handle);
            if (!state)
                return;
            const uint8_t status_byte = static_cast<uint8_t>(param1 & 0xff);
            const uint8_t first_data_byte = static_cast<uint8_t>((param1 >> 8) & 0xff);
            const uint8_t second_data_byte = static_cast<uint8_t>((param1 >> 16) & 0xff);
            const DWORD elapsed_ms = param2;
            const size_t len = GetMessageLength(status_byte);
            const uint8_t kData[] = { status_byte, first_data_byte, second_data_byte };
            std::vector<uint8_t> data;
            data.assign(kData, kData + len);
            DCHECK_LE(len, arraysize(kData));
            // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
            // called as the origin of |elapsed_ms|.
            // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
            // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
            const base::TimeTicks event_time = state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
            task_thread_.task_runner()->PostTask(
                FROM_HERE, base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread, base::Unretained(this), state->port_index, data, event_time));
        }

        void OnMidiInLongDataOnMultimediaThread(HMIDIIN midi_in_handle,
            DWORD_PTR param1,
            DWORD_PTR param2)
        {
            auto state = GetInputDeviceFromHandle(midi_in_handle);
            if (!state)
                return;
            MIDIHDR* header = reinterpret_cast<MIDIHDR*>(param1);
            const DWORD elapsed_ms = param2;
            MMRESULT result = MMSYSERR_NOERROR;
            if (destructor_started) {
                if (state->midi_header && (state->midi_header->dwFlags & MHDR_PREPARED) == MHDR_PREPARED) {
                    result = midiInUnprepareHeader(state->midi_handle, state->midi_header.get(),
                        sizeof(*state->midi_header));
                    DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                        << "Failed to uninitialize input buffer: "
                        << GetInErrorMessage(result);
                }
                return;
            }
            if (header->dwBytesRecorded > 0) {
                const uint8_t* src = reinterpret_cast<const uint8_t*>(header->lpData);
                std::vector<uint8_t> data;
                data.assign(src, src + header->dwBytesRecorded);
                // MIM_DATA/MIM_LONGDATA message treats the time when midiInStart() is
                // called as the origin of |elapsed_ms|.
                // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757284.aspx
                // http://msdn.microsoft.com/en-us/library/windows/desktop/dd757286.aspx
                const base::TimeTicks event_time = state->start_time + base::TimeDelta::FromMilliseconds(elapsed_ms);
                task_thread_.task_runner()->PostTask(
                    FROM_HERE,
                    base::Bind(&MidiServiceWinImpl::ReceiveMidiDataOnTaskThread,
                        base::Unretained(this), state->port_index, data,
                        event_time));
            }
            result = midiInAddBuffer(state->midi_handle, header, sizeof(*header));
            DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                << "Failed to attach input buffer: " << GetInErrorMessage(result)
                << "port number:" << state->port_index;
        }

        void OnMidiInCloseOnMultimediaThread(HMIDIIN midi_in_handle)
        {
            auto state = GetInputDeviceFromHandle(midi_in_handle);
            if (!state)
                return;
            const uint32_t port_number = state->port_index;
            const auto device_info(state->device_info);
            {
                base::AutoLock auto_lock(input_ports_lock_);
                input_device_map_.erase(state->midi_handle);
                input_ports_[port_number] = nullptr;
                input_ports_ages_[port_number] += 1;
                unused_input_ports_[device_info].push(port_number);
            }
            task_thread_.task_runner()->PostTask(
                FROM_HERE,
                base::Bind(&MidiServiceWinImpl::SetInputPortStateOnTaskThread,
                    base::Unretained(this), port_number,
                    PortState::DISCONNECTED));
        }

        static void CALLBACK
        OnMidiOutEventOnMainlyMultimediaThread(HMIDIOUT midi_out_handle,
            UINT message,
            DWORD_PTR instance,
            DWORD_PTR param1,
            DWORD_PTR param2)
        {
            MidiServiceWinImpl* self = reinterpret_cast<MidiServiceWinImpl*>(instance);
            if (!self)
                return;
            switch (message) {
            case MOM_OPEN:
                self->OnMidiOutOpen(midi_out_handle, param1, param2);
                break;
            case MOM_DONE:
                self->OnMidiOutDoneOnMultimediaThread(midi_out_handle, param1);
                break;
            case MOM_CLOSE:
                self->OnMidiOutCloseOnMultimediaThread(midi_out_handle);
                break;
            }
        }

        void OnMidiOutOpen(HMIDIOUT midi_out_handle,
            DWORD_PTR param1,
            DWORD_PTR param2)
        {
            UINT device_id = 0;
            MMRESULT result = midiOutGetID(midi_out_handle, &device_id);
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "midiOutGetID failed: " << GetOutErrorMessage(result);
                return;
            }
            MIDIOUTCAPS2W caps = {};
            result = midiOutGetDevCaps(
                device_id, reinterpret_cast<LPMIDIOUTCAPSW>(&caps), sizeof(caps));
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "midiInGetDevCaps failed: " << GetOutErrorMessage(result);
                return;
            }
            auto state = make_scoped_refptr(new MidiOutputDeviceState(MidiDeviceInfo(caps)));
            state->midi_handle = midi_out_handle;
            const auto& state_device_info = state->device_info;
            if (IsUnsupportedDevice(state_device_info)) {
                task_thread_.task_runner()->PostTask(
                    FROM_HERE, base::Bind(&CloseOutputPortOnTaskThread, midi_out_handle));
                return;
            }
            bool add_new_port = false;
            uint32_t port_number = 0;
            {
                base::AutoLock auto_lock(output_ports_lock_);
                const auto it = unused_output_ports_.find(state_device_info);
                if (it == unused_output_ports_.end()) {
                    port_number = static_cast<uint32_t>(output_ports_.size());
                    add_new_port = true;
                    output_ports_.push_back(nullptr);
                    output_ports_ages_.push_back(0);
                } else {
                    port_number = it->second.top();
                    it->second.pop();
                    if (it->second.empty())
                        unused_output_ports_.erase(it);
                }
                output_ports_[port_number] = state;
                output_ports_ages_[port_number] += 1;
                output_device_map_[output_ports_[port_number]->midi_handle] = output_ports_[port_number];
                output_ports_[port_number]->port_index = port_number;
                output_ports_[port_number]->port_age = output_ports_ages_[port_number];
            }
            if (add_new_port) {
                const MidiPortInfo port_info(
                    // TODO(toyoshim): Use a hash ID insted. crbug.com/467448
                    base::IntToString(static_cast<int>(port_number)),
                    GetManufacturerName(state_device_info),
                    base::WideToUTF8(state_device_info.product_name),
                    MmversionToString(state_device_info.driver_version),
                    PortState::OPENED);
                task_thread_.task_runner()->PostTask(
                    FROM_HERE, base::Bind(&MidiServiceWinImpl::AddOutputPortOnTaskThread, base::Unretained(this), port_info));
            } else {
                task_thread_.task_runner()->PostTask(
                    FROM_HERE,
                    base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
                        base::Unretained(this), port_number,
                        PortState::CONNECTED));
            }
        }

        void OnMidiOutDoneOnMultimediaThread(HMIDIOUT midi_out_handle,
            DWORD_PTR param1)
        {
            auto state = GetOutputDeviceFromHandle(midi_out_handle);
            if (!state)
                return;
            // Take ownership of the MIDIHDR object.
            ScopedMIDIHDR header(reinterpret_cast<MIDIHDR*>(param1));
            if (!header)
                return;
            MMRESULT result = midiOutUnprepareHeader(state->midi_handle, header.get(),
                sizeof(*header));
            DLOG_IF(ERROR, result != MMSYSERR_NOERROR)
                << "Failed to uninitialize output buffer: "
                << GetOutErrorMessage(result);
        }

        void OnMidiOutCloseOnMultimediaThread(HMIDIOUT midi_out_handle)
        {
            auto state = GetOutputDeviceFromHandle(midi_out_handle);
            if (!state)
                return;
            const uint32_t port_number = state->port_index;
            const auto device_info(state->device_info);
            {
                base::AutoLock auto_lock(output_ports_lock_);
                output_device_map_.erase(state->midi_handle);
                output_ports_[port_number] = nullptr;
                output_ports_ages_[port_number] += 1;
                unused_output_ports_[device_info].push(port_number);
                state->closed = true;
            }
            task_thread_.task_runner()->PostTask(
                FROM_HERE,
                base::Bind(&MidiServiceWinImpl::SetOutputPortStateOnTaskThread,
                    base::Unretained(this), port_number,
                    PortState::DISCONNECTED));
        }

        /////////////////////////////////////////////////////////////////////////////
        // Callbacks on the sender thread.
        /////////////////////////////////////////////////////////////////////////////

        void AssertOnSenderThread()
        {
            DCHECK_EQ(sender_thread_.GetThreadId(), base::PlatformThread::CurrentId());
        }

        void SendOnSenderThread(uint32_t port_number,
            uint64_t port_age,
            const std::vector<uint8_t>& data,
            base::TimeTicks time)
        {
            AssertOnSenderThread();
            if (destructor_started) {
                LOG(ERROR) << "ThreadSafeSendData failed because MidiServiceWinImpl is "
                              "being destructed. port: "
                           << port_number;
            }
            auto state = GetOutputDeviceFromPort(port_number);
            if (!state) {
                LOG(ERROR) << "ThreadSafeSendData failed due to an invalid port number. "
                           << "port: " << port_number;
                return;
            }
            if (state->closed) {
                LOG(ERROR)
                    << "ThreadSafeSendData failed because target port is already closed."
                    << "port: " << port_number;
                return;
            }
            if (state->port_age != port_age) {
                LOG(ERROR)
                    << "ThreadSafeSendData failed because target port is being closed."
                    << "port: " << port_number << "expected port age: " << port_age
                    << "actual port age: " << state->port_age;
            }

            // MIDI Running status must be filtered out.
            MidiMessageQueue message_queue(false);
            message_queue.Add(data);
            std::vector<uint8_t> message;
            while (true) {
                if (destructor_started)
                    break;
                if (state->closed)
                    break;
                message_queue.Get(&message);
                if (message.empty())
                    break;
                // SendShortMidiMessageInternal can send a MIDI message up to 3 bytes.
                if (message.size() <= 3)
                    SendShortMidiMessageInternal(state->midi_handle, message);
                else
                    SendLongMidiMessageInternal(state->midi_handle, message);
            }
        }

        /////////////////////////////////////////////////////////////////////////////
        // Callbacks on the task thread.
        /////////////////////////////////////////////////////////////////////////////

        void AssertOnTaskThread()
        {
            DCHECK_EQ(task_thread_.GetThreadId(), base::PlatformThread::CurrentId());
        }

        void UpdateDeviceListOnTaskThread()
        {
            AssertOnTaskThread();
            const UINT num_in_devices = midiInGetNumDevs();
            for (UINT device_id = 0; device_id < num_in_devices; ++device_id) {
                // Here we use |CALLBACK_FUNCTION| to subscribe MIM_DATA, MIM_LONGDATA,
                // MIM_OPEN, and MIM_CLOSE events.
                // - MIM_DATA: This is the only way to get a short MIDI message with
                //     timestamp information.
                // - MIM_LONGDATA: This is the only way to get a long MIDI message with
                //     timestamp information.
                // - MIM_OPEN: This event is sent the input device is opened. Note that
                //     this message is called on the caller thread.
                // - MIM_CLOSE: This event is sent when 1) midiInClose() is called, or 2)
                //     the MIDI device becomes unavailable for some reasons, e.g., the
                //     cable is disconnected. As for the former case, HMIDIOUT will be
                //     invalidated soon after the callback is finished. As for the later
                //     case, however, HMIDIOUT continues to be valid until midiInClose()
                //     is called.
                HMIDIIN midi_handle = kInvalidMidiInHandle;
                const MMRESULT result = midiInOpen(
                    &midi_handle, device_id,
                    reinterpret_cast<DWORD_PTR>(&OnMidiInEventOnMainlyMultimediaThread),
                    reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
                DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
                    << "Failed to open output device. "
                    << " id: " << device_id << " message: " << GetInErrorMessage(result);
            }

            const UINT num_out_devices = midiOutGetNumDevs();
            for (UINT device_id = 0; device_id < num_out_devices; ++device_id) {
                // Here we use |CALLBACK_FUNCTION| to subscribe MOM_DONE, MOM_OPEN, and
                // MOM_CLOSE events.
                // - MOM_DONE: SendLongMidiMessageInternal() relies on this event to clean
                //     up the backing store where a long MIDI message is stored.
                // - MOM_OPEN: This event is sent the output device is opened. Note that
                //     this message is called on the caller thread.
                // - MOM_CLOSE: This event is sent when 1) midiOutClose() is called, or 2)
                //     the MIDI device becomes unavailable for some reasons, e.g., the
                //     cable is disconnected. As for the former case, HMIDIOUT will be
                //     invalidated soon after the callback is finished. As for the later
                //     case, however, HMIDIOUT continues to be valid until midiOutClose()
                //     is called.
                HMIDIOUT midi_handle = kInvalidMidiOutHandle;
                const MMRESULT result = midiOutOpen(
                    &midi_handle, device_id,
                    reinterpret_cast<DWORD_PTR>(&OnMidiOutEventOnMainlyMultimediaThread),
                    reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION);
                DLOG_IF(ERROR, result != MMSYSERR_NOERROR && result != MMSYSERR_ALLOCATED)
                    << "Failed to open output device. "
                    << " id: " << device_id << " message: " << GetOutErrorMessage(result);
            }
        }

        void StartInputDeviceOnTaskThread(HMIDIIN midi_in_handle)
        {
            AssertOnTaskThread();
            auto state = GetInputDeviceFromHandle(midi_in_handle);
            if (!state)
                return;
            MMRESULT result = midiInPrepareHeader(state->midi_handle, state->midi_header.get(),
                sizeof(*state->midi_header));
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "Failed to initialize input buffer: "
                            << GetInErrorMessage(result);
                return;
            }
            result = midiInAddBuffer(state->midi_handle, state->midi_header.get(),
                sizeof(*state->midi_header));
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "Failed to attach input buffer: "
                            << GetInErrorMessage(result);
                return;
            }
            result = midiInStart(state->midi_handle);
            if (result != MMSYSERR_NOERROR) {
                DLOG(ERROR) << "Failed to start input port: "
                            << GetInErrorMessage(result);
                return;
            }
            state->start_time = base::TimeTicks::Now();
            state->start_time_initialized = true;
        }

        void CompleteInitializationOnTaskThread(Result result)
        {
            AssertOnTaskThread();
            delegate_->OnCompleteInitialization(result);
        }

        void ReceiveMidiDataOnTaskThread(uint32_t port_index,
            std::vector<uint8_t> data,
            base::TimeTicks time)
        {
            AssertOnTaskThread();
            delegate_->OnReceiveMidiData(port_index, data, time);
        }

        void AddInputPortOnTaskThread(MidiPortInfo info)
        {
            AssertOnTaskThread();
            delegate_->OnAddInputPort(info);
        }

        void AddOutputPortOnTaskThread(MidiPortInfo info)
        {
            AssertOnTaskThread();
            delegate_->OnAddOutputPort(info);
        }

        void SetInputPortStateOnTaskThread(uint32_t port_index, PortState state)
        {
            AssertOnTaskThread();
            delegate_->OnSetInputPortState(port_index, state);
        }

        void SetOutputPortStateOnTaskThread(uint32_t port_index, PortState state)
        {
            AssertOnTaskThread();
            delegate_->OnSetOutputPortState(port_index, state);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Fields:
        /////////////////////////////////////////////////////////////////////////////

        // Does not take ownership.
        MidiServiceWinDelegate* delegate_;

        base::ThreadChecker thread_checker_;

        base::Thread sender_thread_;
        base::Thread task_thread_;

        base::Lock input_ports_lock_;
        base::hash_map<HMIDIIN, scoped_refptr<MidiInputDeviceState>>
            input_device_map_; // GUARDED_BY(input_ports_lock_)
        PortNumberCache unused_input_ports_; // GUARDED_BY(input_ports_lock_)
        std::vector<scoped_refptr<MidiInputDeviceState>>
            input_ports_; // GUARDED_BY(input_ports_lock_)
        std::vector<uint64_t> input_ports_ages_; // GUARDED_BY(input_ports_lock_)

        base::Lock output_ports_lock_;
        base::hash_map<HMIDIOUT, scoped_refptr<MidiOutputDeviceState>>
            output_device_map_; // GUARDED_BY(output_ports_lock_)
        PortNumberCache unused_output_ports_; // GUARDED_BY(output_ports_lock_)
        std::vector<scoped_refptr<MidiOutputDeviceState>>
            output_ports_; // GUARDED_BY(output_ports_lock_)
        std::vector<uint64_t> output_ports_ages_; // GUARDED_BY(output_ports_lock_)

        // True if one thread reached MidiServiceWinImpl::~MidiServiceWinImpl(). Note
        // that MidiServiceWinImpl::~MidiServiceWinImpl() is blocked until
        // |sender_thread_|, and |task_thread_| are stopped.
        // This flag can be used as the signal that when background tasks must be
        // interrupted.
        // TODO(toyoshim): Use std::atomic<bool> when it is allowed.
        volatile bool destructor_started;

        DISALLOW_COPY_AND_ASSIGN(MidiServiceWinImpl);
    };

} // namespace

MidiManagerWin::MidiManagerWin()
{
}

MidiManagerWin::~MidiManagerWin()
{
}

void MidiManagerWin::StartInitialization()
{
    midi_service_.reset(new MidiServiceWinImpl);
    // Note that |CompleteInitialization()| will be called from the callback.
    midi_service_->InitializeAsync(this);
}

void MidiManagerWin::Finalize()
{
    midi_service_.reset();
}

void MidiManagerWin::DispatchSendMidiData(MidiManagerClient* client,
    uint32_t port_index,
    const std::vector<uint8_t>& data,
    double timestamp)
{
    if (!midi_service_)
        return;

    base::TimeTicks time_to_send = base::TimeTicks::Now();
    if (timestamp != 0.0) {
        time_to_send = base::TimeTicks() + base::TimeDelta::FromMicroseconds(timestamp * base::Time::kMicrosecondsPerSecond);
    }
    midi_service_->SendMidiDataAsync(port_index, data, time_to_send);

    // TOOD(toyoshim): This calculation should be done when the date is actually
    // sent.
    client->AccumulateMidiBytesSent(data.size());
}

void MidiManagerWin::OnCompleteInitialization(Result result)
{
    CompleteInitialization(result);
}

void MidiManagerWin::OnAddInputPort(MidiPortInfo info)
{
    AddInputPort(info);
}

void MidiManagerWin::OnAddOutputPort(MidiPortInfo info)
{
    AddOutputPort(info);
}

void MidiManagerWin::OnSetInputPortState(uint32_t port_index, PortState state)
{
    SetInputPortState(port_index, state);
}

void MidiManagerWin::OnSetOutputPortState(uint32_t port_index,
    PortState state)
{
    SetOutputPortState(port_index, state);
}

void MidiManagerWin::OnReceiveMidiData(uint32_t port_index,
    const std::vector<uint8_t>& data,
    base::TimeTicks time)
{
    ReceiveMidiData(port_index, &data[0], data.size(), time);
}

MidiManager* MidiManager::Create()
{
    if (base::FeatureList::IsEnabled(features::kMidiManagerWinrt) && base::win::GetVersion() >= base::win::VERSION_WIN10)
        return new MidiManagerWinrt();
    return new MidiManagerWin();
}

} // namespace midi
